/**
 * Xilinx PL/AXI-GPIO 驱动适配层
 * 张勇 / 41204@qq.com
 */
#include <stdbool.h>
#include <xparameters.h>
#include <xgpio.h>
#include <xintc.h>
#include "gpio.h"

/// AXI-GPIO Driver 实例
static XGpio  _gpiopl_inst[XPAR_XGPIO_NUM_INSTANCES];
static XGpio* _gpiopl[XPAR_XGPIO_NUM_INSTANCES] = { NULL };

// 保存各通道各个 pin 的引脚状态
static uint32_t _gpiopl_val[XPAR_XGPIO_NUM_INSTANCES][2] = { {0, 0} };

// 更新通道各引脚电平状态
static void gpiopl_val_refresh(uint16_t devid, uint16_t chid) {
	_gpiopl_val[devid][chid - 1] = XGpio_DiscreteRead(_gpiopl[devid], chid);
}

std_err_t XGPIO_Config(uint16_t devid, uint16_t chid, uint16_t pin, gpio_mode_t mode) {
	if(devid >= XPAR_XGPIO_NUM_INSTANCES || chid > 2 || mode >= GPIO_MODE_MAX) return STD_ERR_INVALID_ARG;

	// 如果 GPIO Driver 实例未初始化，则先初始化一次
	if(!_gpiopl[devid]) {
		s32 xStatus = XGpio_Initialize(&_gpiopl_inst[devid], devid); if(XST_SUCCESS != xStatus) return STD_ERR_FAIL;
		_gpiopl[devid] = &_gpiopl_inst[devid];
	}

	uint32_t iomask = XGpio_GetDataDirection(_gpiopl[devid], chid); // mask bit: 0=输出，1=输入
	if(mode == GPIO_MODE_DISABLE) {
		XGpio_SetDataDirection(_gpiopl[devid], chid, iomask | (1 << pin)); // 设置为输入
	} else if(mode == GPIO_MODE_IN) {
		XGpio_SetDataDirection(_gpiopl[devid], chid, iomask | (1 << pin));
	} else if(mode == GPIO_MODE_OUT) {
		XGpio_SetDataDirection(_gpiopl[devid], chid, iomask & (~(1 << pin)));
	} else {
		return STD_ERR_INVALID_ARG;
	}
	return STD_ERR_OK;
}

std_err_t XGPIO_SetLevel(uint16_t devid, uint16_t chid, uint16_t pin, uint8_t level) {
	if(level) {
		XGpio_DiscreteSet(_gpiopl[devid], chid, 1 << pin);
	} else {
		XGpio_DiscreteClear(_gpiopl[devid], chid, 1 << pin);
	}
	return STD_ERR_OK;
}

uint8_t XGPIO_GetLevel(uint16_t devid, uint16_t chid, uint16_t pin) {
	uint32_t val = XGpio_DiscreteRead(_gpiopl[devid], chid);
	return (val >> pin) & 1;
}

uint8_t XGPIO_BidiIn(uint16_t devid, uint16_t chid, uint16_t pin) {
	// 如果当前配置不是 IN 模式，则先配置为 IN 模式
	uint32_t iomask = XGpio_GetDataDirection(_gpiopl[devid], chid);
	if((iomask & (1 << pin)) != 1) XGpio_SetDataDirection(_gpiopl[devid], chid, iomask | (1 << pin));
	return XGPIO_GetLevel(devid, chid, pin);
}

std_err_t XGPIO_BidiOut(uint16_t devid, uint16_t chid, uint16_t pin, uint8_t level) {
	// 如果当前配置不是 OUT 模式，则先配置为 OUT 模式
	uint32_t iomask = XGpio_GetDataDirection(_gpiopl[devid], chid);
	if((iomask & (1 << pin)) != 0) XGpio_SetDataDirection(_gpiopl[devid], chid, iomask & (~(1 << pin)));
	XGPIO_SetLevel(devid, chid, pin, level);
	return STD_ERR_OK;
}

void XGPIO_EXTI_IRQ_Handler(void *cookie) {
}

std_err_t XGPIO_EXTI_Config(uint16_t devid, uint16_t chid, uint16_t pin, uint32_t irqn, uint8_t priority, mcu_isr_t isr, void *cookie) {
	if(!isr) return STD_ERR_INVALID_ARG;

	// 设置 GIC 中断触发类型
	// 注意: GIC 检测 AXI-GPIO 中断时，只能采用上升沿触发(0x03)
	std_err_t err = mcu_gic_config(irqn, priority, 0x03); if(err) return err;
	// 为中断设置中断处理函数
	return mcu_register_handler(irqn, isr, cookie);
}

std_err_t XGPIO_EXTI_Enable(uint16_t devid, uint16_t chid, uint16_t pin, uint32_t irqn, bool enable) {
	if(enable) {
		XGpio_InterruptEnable(_gpiopl[devid], 1 << (chid - 1)); // 使能 AXI-GPIO 通道的中断
		XGpio_InterruptGlobalEnable(_gpiopl[devid]);            // 使能 AXI-GPIO 的全局中断
		return mcu_gic_enable(irqn, enable);                    // 使能 GIC 对应 IRQN 的中断
	} else {
		//mcu_irq_enable(irqn, enable);                         // 禁用 GIC 对应 IRQN 的中断 ?
		//XGpio_InterruptGlobalDisable(_gpiopl[devid]);         // 禁用 AXI-GPIO 全局的中断 ?
		XGpio_InterruptDisable(_gpiopl[devid], 1 << (chid - 1));// 禁用 AXI-GPIO 通道的中断 ?
		return STD_ERR_OK;
	}
}

bool XGPIO_EXTI_Flag(uint16_t devid, uint16_t chid, uint16_t pin) {
	// 判断所属 channel 中断标志
	uint32_t flags = XGpio_InterruptGetStatus(_gpiopl[devid]);
	if((flags & (1 << chid)) == 0) return false;

	// 获取引脚电平
	uint32_t old = _gpiopl_val[devid][chid - 1];
	uint32_t val = XGpio_DiscreteRead(_gpiopl[devid], chid);

	// 计算电平发生了变化的引脚
	val = old ^ val;
	return (val & (1 << pin)) != 0;
}

std_err_t XGPIO_EXTI_Clear(uint16_t devid, uint16_t chid) {
	// ISR 寄存器读出来再写入就会清除 channel 中断标志
	uint32_t flags = XGpio_ReadReg(_gpiopl[devid]->BaseAddress, XGPIO_ISR_OFFSET);
	XGpio_WriteReg(_gpiopl[devid]->BaseAddress, XGPIO_ISR_OFFSET, flags);
	// 更新通道各引脚电平状态
	gpiopl_val_refresh(devid, chid);
	return STD_ERR_OK;
}

std_err_t XGPIO_EXTI_TrigMannually(uint16_t devid, uint16_t chid) {
	XGpio_WriteReg(_gpiopl[devid]->BaseAddress, XGPIO_ISR_OFFSET, 0x03);
	return STD_ERR_OK;
}
